﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AsyncCamera.Camera.Structure;
using Incendios0_1.Camera;
using Incendios0_1.Camera.media;

namespace AsyncCamera.Camera.media
{
    /// <summary>
    /// Multimedia Container for Sinks, Filters and Sources. 
    /// </summary>
    /// <remarks>
    /// The media elements contained in this class will be data sincronized 
    /// and can be manipulated together. Is intented for making groups
    /// of multimedia elements that works together. 
    /// </remarks>
    public class MediaGroup
    {
        private MediaException lastError;
        /// <summary>
        /// List of sources
        /// </summary>
        protected LinkedList<MediaSource> sources;
        /// <summary>
        /// List of filters
        /// </summary>
        protected LinkedList<MediaFilter> filters;
        /// <summary>
        /// List of sinks
        /// </summary>
        protected LinkedList<MediaSink> sinks;
        /// <summary>
        /// Displays can be attached to this output for multiple views
        /// </summary>
        protected MediaSource defaultOutput;
        /// <summary>
        /// Current media state of the group
        /// </summary>
        protected MediaState currentState;
        public MediaState CurrentState
        {
            get { return currentState; }
        }
        /// <summary>
        /// Current FPS of the group
        /// </summary>
        protected double fps;
        public double FPS
        {
            get { return this.fps; }
            set
            {
                if (currentState == MediaState.Pause || currentState == MediaState.Stop)
                {
                    foreach(MediaSource src in sources)
                    {
                        src.FPS = value;
                    }
                    foreach (MediaFilter flt in filters)
                    {
                        flt.FPS = value;
                    }
                    foreach (MediaSink snk in sinks)
                    {
                        snk.FPS = value;
                    }
                    this.fps = value;
                }
            }
        }

        /// <summary>
        /// Adds a filter to the group
        /// </summary>
        /// <param name="group">Group instance, usually the current group</param>
        /// <param name="newFilter">the filter to be added</param>
        /// <returns>the current group instance</returns>
        public static MediaGroup operator +(MediaGroup group, MediaFilter newFilter)
        {
            group.checkForState(MediaState.Stop);
            group.filters.AddLast(newFilter);
            return group;
        }

        /// <summary>
        /// Adds a source to the group
        /// </summary>
        /// <param name="group">Group instance, usually the current group</param>
        /// <param name="newSource">the source to be added</param>
        /// <returns>the current group instance</returns>
        public static MediaGroup operator +(MediaGroup group, MediaSource newSource)
        {
            group.checkForState(MediaState.Stop);
            group.sources.AddLast(newSource);
            return group;
        }

        /// <summary>
        /// Adds a sink to the group
        /// </summary>
        /// <param name="group">Group instance, usually the current group</param>
        /// <param name="newSink">the sink to be added</param>
        /// <returns>the current group instance</returns>
        public static MediaGroup operator +(MediaGroup group, MediaSink newSink)
        {
            group.checkForState(MediaState.Stop);
            group.sinks.AddLast(newSink);
            return group;
        }

        /// <summary>
        /// Basic Contructor. Initializes all variables, sets the current state to MediaState.Stop
        /// and the current FPS to 0
        /// </summary>
        public MediaGroup()
        {
            sources = new LinkedList<MediaSource>();
            filters = new LinkedList<MediaFilter>();
            sinks = new LinkedList<MediaSink>();
            currentState = MediaState.Stop;
            fps = 0;
        }

        /// <summary>
        /// Constructor that sets the current fps 
        /// </summary>
        /// <param name="fps">The fps to be set as current fps</param>
        /// <seealso cref="MediaGroup()">
        public MediaGroup(double fps):this()
        {
            this.FPS = fps;
        }

        /// <summary>
        /// Constructor that sets the current state and fps to the input given
        /// </summary>
        /// <param name="state">The current State value to be set</param>
        /// <param name="fps">The current fps value to be set</param>
        public MediaGroup(MediaState state, double fps)
            : this(fps)
        {
            this.currentState = state;
        }

        /// <summary>
        /// This method invokes the <c>Start()</c> method of all media elements contained 
        /// </summary>
        public virtual void Start()
        {
            lastError = null;
            if (currentState != MediaState.Play)
            {
                try
                {
                    foreach (MediaSource src in sources)
                    {
                        src.Start();
                    }
                    foreach (MediaFilter flt in filters)
                    {
                        flt.Start();
                    }
                    foreach (MediaSink snk in sinks)
                    {
                        snk.Start();
                    }
                    currentState = MediaState.Play;
                }
                catch(MediaException e)
                {
                    lastError = e;
                    Stop();
                }
            }
        }

        /// <summary>
        /// This method invokes the <c>Pause()</c> method of all media elements contained 
        /// </summary>
        public virtual void Pause()
        {
            if (currentState != MediaState.Pause)
            {
                foreach (MediaSink snk in sinks)
                {
                    snk.Pause();
                }
                foreach (MediaFilter flt in filters)
                {
                    flt.Pause();
                }
                foreach (MediaSource src in sources)
                {
                    src.Pause();
                }
                currentState = MediaState.Pause;
            }
        }

        /// <summary>
        /// This method invokes the <c>Stop()</c> method of all media elements contained 
        /// </summary>
        public virtual void Stop()
        {
            if (currentState != MediaState.Stop)
            {
                foreach (MediaSink snk in sinks)
                {
                    snk.Stop();
                }
                foreach (MediaFilter flt in filters)
                {
                    flt.Stop();
                }
                foreach (MediaSource src in sources)
                {
                    src.Stop();
                }
                currentState = MediaState.Pause;
            }
        }

        /// <summary>
        /// This method hooks up a new filter to the default output
        /// </summary>
        public virtual void PlugIn(MediaFilter newFilter)
        {
            if (defaultOutput != null)
                newFilter += defaultOutput;
        }

        /// <summary>
        /// This method hooks up a new sink to the default output
        /// </summary>
        public virtual void PlugIn(MediaSink newSink)
        {
            if (defaultOutput != null)
                newSink += defaultOutput;
        }

        /// <summary>
        /// Sets the default output to the given filter. If the filter is not
        /// into the group it will be added
        /// </summary>
        /// <param name="newOutput">The new default output</param>
        public virtual void setDefaultOutput(MediaFilter newOutput)
        {
            checkForState(MediaState.Stop);
            if (filters.Contains(newOutput))
            {
                defaultOutput = newOutput;
            }
            else
            {
                checkForState(MediaState.Stop);
                sources.AddLast(newOutput);
                defaultOutput = newOutput;
            }
        }

        /// <summary>
        /// Sets the default output to the given source. If the filter is not
        /// into the group it will be added
        /// </summary>
        /// <param name="newOutput">The new default output</param>
        public virtual void setDefaultOutput(MediaSource newOutput)
        {
            if (sources.Contains(newOutput))
            {
                defaultOutput = newOutput;
            }
            else
            {
                checkForState(MediaState.Stop);
                sources.AddLast(newOutput);
                defaultOutput = newOutput;
            }
        }

        /// <summary>
        /// Validates if the given state is the current state, if not it throws a <c>MediaException</c>
        /// </summary>
        /// <param name="check">The desired state to be checked</param>
        protected virtual void checkForState(MediaState check)
        {
            if (this.currentState != check)
                throw new MediaException("The current state is different from the checked");
        }
    }
}
